Skip to content

Add global keyboard shortcuts for approve/deny permissions#49

Open
lancehambly wants to merge 10 commits into
farouqaldori:mainfrom
lancehambly:feature/keyboard-shortcuts
Open

Add global keyboard shortcuts for approve/deny permissions#49
lancehambly wants to merge 10 commits into
farouqaldori:mainfrom
lancehambly:feature/keyboard-shortcuts

Conversation

@lancehambly

@lancehambly lancehambly commented Feb 24, 2026

Copy link
Copy Markdown

Context

Hey! Love this project — the Dynamic Island UX for Claude Code is brilliant. I kept finding myself reaching for keyboard shortcuts to approve/deny permissions without having to mouse up to the notch, especially during fast iteration loops. So I put together this feature as a suggestion.

These are proposed changes only — I don't have any permissions on this repo. Happy for you to take, adapt, cherry-pick, or ignore any of this. Just wanted to share what worked well for me and a colleague.

What this adds

System-wide keyboard shortcuts for approving/denying Claude Code permission requests, plus several UX improvements to the notch behavior around permissions.

Keyboard shortcuts

Shortcut Action
⌘⇧Y Approve the selected pending permission
⌘⇧N Deny the selected pending permission
⌘⇧↑ Cycle selection to previous pending session
⌘⇧↓ Cycle selection to next pending session
  • Shortcuts work globally from any app — uses Carbon RegisterEventHotKey, no Accessibility permission required
  • Shortcuts are configurable via a new "Keyboard Shortcuts" row in the settings menu, with a shortcut recorder UI
  • Can be enabled/disabled from settings

Multi-session approval selection

When multiple Claude sessions have pending permission requests simultaneously:

  • An amber accent bar on the left edge of the row indicates which session the shortcut will act on
  • ⌘⇧↑ / ⌘⇧↓ cycles between pending sessions (wraps around)
  • First pending session is auto-selected by default
  • Selection auto-reconciles when sessions resolve or new ones arrive
  • A count badge (e.g. "2") appears on the closed notch when 2+ sessions need approval

Notch behavior improvements

  • Always expands for permissions — removed the TerminalVisibilityDetector check that suppressed expansion when a terminal was visible. Users need to see what tool is requesting approval.
  • Persistent while pending — clicking outside the notch no longer dismisses it when there are unanswered permission requests. Clicks still pass through to the underlying app.
  • Auto-retracts when done — after all permissions are resolved, the notch closes automatically (0.8s for shortcut-triggered, 5s for manually-opened). Only applies when showing the instances list, not when user is in chat or menu.
  • Dynamic sizing — instances list height scales to content (~52pt per row) instead of a fixed 320pt, clamped between 120-400pt.

Design decisions

  • Carbon RegisterEventHotKey over NSEvent.addGlobalMonitorForEvents(.keyDown) — the latter doesn't reliably fire for keyboard events when the app isn't focused. Carbon hotkeys are the proper macOS API for this and don't require Accessibility permission.
  • KeyCombo model is Codable and persisted via UserDefaults (through AppSettings) so custom bindings survive app restarts.
  • Selection state lives on NotchViewModel — it's UI state, not session state, and needs to be accessible from both SwiftUI views and the shortcut handler.
  • Sorted approval IDs in the cycling logic match the visual sort order in ClaudeInstancesView so what the user sees matches what they cycle through.

Files changed

New files (3)

File Purpose
Models/KeyboardShortcut.swift KeyCombo model — key code + modifier flags, Codable, human-readable displayString (e.g. "⌘⇧Y"), full Carbon key code → string mapping
Services/Shortcuts/KeyboardShortcutHandler.swift Singleton that registers Carbon hotkeys, routes events to approve/deny/cycle handlers, selection-aware targeting
UI/Components/ShortcutSettingsRow.swift Expandable settings row with enable/disable toggle, shortcut recorder for each binding, reset to defaults

Modified files (5)

File Change
Core/Settings.swift Added shortcutsEnabled, approveShortcut, denyShortcut to AppSettings with UserDefaults persistence
Core/NotchViewModel.swift Added selectedPendingSessionId, hasPendingPermissions, reconcilePendingSelection(), cyclePendingSelection(), dynamic instanceCount-based sizing
UI/Views/NotchView.swift Wired shortcut handler startup, selection reconciliation in onChange, count badge in closed header, auto-retract timers, always-expand for permissions
UI/Views/NotchMenuView.swift Added ShortcutSettingsRow() to the appearance settings section
UI/Views/ClaudeInstancesView.swift Added isSelected parameter to InstanceRow, renders amber left accent bar when selected + waiting for approval

Testing done

Tested manually on macOS 15.6, MacBook Pro with notch:

  • Single permission request — ⌘⇧Y approves from another app
  • Single permission request — ⌘⇧N denies from another app
  • Two concurrent permission requests — ⌘⇧↑/↓ cycles selection, accent bar moves
  • Approve selected session — remaining session auto-selects
  • Notch stays open on click-away while permissions pending
  • Notch auto-retracts after all permissions resolved
  • Custom shortcut recording in settings
  • Dynamic notch height with 1 vs multiple sessions
  • Count badge shows on closed notch with 2+ pending

Screenshots

(Built and tested locally — happy to provide screenshots/recordings if helpful)


Thanks again for building Claude Island — it's become part of my daily workflow. Let me know if you have any questions about the implementation or want me to adjust anything.

🤖 Generated with Claude Code

lancehambly and others added 10 commits February 24, 2026 12:25
Adds macOS-level keyboard shortcuts (⌘⇧Y to approve, ⌘⇧N to deny)
that work system-wide without needing to click the notch UI. Includes
configurable shortcut recording in settings, visual flash feedback on
the notch, and audio confirmation sounds.

New files:
- KeyboardShortcut.swift: KeyCombo model with Carbon key code mapping
- KeyboardShortcutHandler.swift: Global/local NSEvent monitors
- ShortcutFeedback.swift: Visual/audio feedback on shortcut activation
- ShortcutSettingsRow.swift: Settings UI with shortcut recorder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes:
1. Replace NSEvent global key monitor with Carbon RegisterEventHotKey
   for true system-wide hotkeys that work regardless of focused app
   (NSEvent global monitors cannot reliably capture keyDown events)
2. Always expand notch for pending permissions, even when a terminal
   is visible — users need to see what tool is requesting approval

Also wire reloadShortcuts() to settings UI so hotkeys re-register
immediately when changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Instance list height now scales to content — ~52pt per row
   instead of a fixed 320pt, clamped between 120-400pt
2. Notch auto-closes 0.8s after all pending permissions are
   resolved (only when opened via notification, not manual click)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When there are unanswered permission requests and the user's terminal
is not visible, clicking outside the island no longer dismisses it.
The click still passes through to the underlying app, but the island
stays visible so the user doesn't lose sight of pending approvals.

If the terminal IS visible (user can see the CLI prompt), outside
clicks dismiss as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When all permissions are resolved and the island is showing the
instances list: retract after 0.8s (keyboard shortcut / notification)
or 5s (manually opened). Click-away already works for non-permission
states. Does not auto-close if user is in chat or menu view.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous auto-retract only triggered when pending permissions
went from non-empty to empty. Completions (waitingForInput) that
arrive while the island is open now also schedule a 5s retract
if there are no outstanding permission requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the terminal visibility check from the click-away guard.
The island now stays open unconditionally when there are pending
permission requests, regardless of whether a terminal is visible.
Users running Claude Code will always have a terminal on screen,
so the previous check was effectively disabling persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When multiple Claude sessions have pending permission requests:

- Amber accent bar on the left edge of the selected row shows
  which session ⌘⇧Y/⌘⇧N will act on
- ⌘⇧↑ / ⌘⇧↓ cycles selection between pending sessions
- Closed notch shows count badge ("2", "3"...) when 2+ pending
- Selection auto-reconciles when sessions resolve or arrive
- Approve/deny shortcuts now target the selected session
  instead of blindly picking the first one

New state: NotchViewModel.selectedPendingSessionId
New hotkeys: Carbon RegisterEventHotKey for ⌘⇧↑ and ⌘⇧↓

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The green/red flash now highlights only the specific row that was
approved/denied, not the entire notch. The flash overlay sits on
InstanceRow and only renders when that row is the selected target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The green/red glow on approve/deny wasn't necessary — the row
disappearing from the list is sufficient feedback. Removes
ShortcutFeedback entirely as nothing uses it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rylax added a commit to rylax/claude-island that referenced this pull request Mar 9, 2026
Kel-Antony added a commit to Kel-Antony/claude-island that referenced this pull request Apr 9, 2026
…arouqaldori#43, farouqaldori#49, farouqaldori#52)

Always Allow (farouqaldori#52):
- Add permissionApprovedAlways event to SessionStore state machine
- Add autoApprovedTools: Set<String> to SessionState — approved tools
  are silently auto-approved on future requests within the same session
- Add Always (green) button to InlineApprovalButtons and ChatApprovalBar
- Island stays open when permissions are pending (can't dismiss by clicking away)

Keyboard shortcuts (farouqaldori#49):
- Add KeyCombo model with Codable + displayString support
- Add KeyboardShortcutHandler using Carbon RegisterEventHotKey for
  true system-wide hotkeys (work regardless of focused app)
  - ⌘⇧Y = Approve, ⌘⇧N = Deny, ⌘⇧↓/↑ = cycle between pending sessions
- Add shortcut settings to AppSettings (UserDefaults backed)
- Add selectedPendingSessionId + hasPendingPermissions to NotchViewModel
- Show amber left-edge indicator on the keyboard-selected pending row
- Instances panel height is now dynamic (52pt per row, clamped 120–400pt)

Deployment target (farouqaldori#43):
- Lower MACOSX_DEPLOYMENT_TARGET from 15.6 → 15.0
rosuH pushed a commit to rosuH/claude-island that referenced this pull request Apr 14, 2026
* feat: add proactive accessibility permission checking

- Add AccessibilityPermissionManager singleton to monitor permission state
- Show startup alert explaining why permission is needed
- Display amber warning icon in closed notch when permission missing
- Update AccessibilityRow with amber styling and reactive state
- Keep notch visible while accessibility warning is shown
- Periodic monitoring detects when user grants permission

Fixes farouqaldori#46

* fix: address review comments from PR farouqaldori#49

- Start periodic monitoring immediately when accessibility permission is
  missing, so UI reliably updates when permission is granted through any
  path (not just via our alert's "Open Settings" button)
- Include accessibilityWarningWidth in expansion calculation for the
  warning-only case, fixing potential layout compression

* fix: improve accessibility permission detection with adaptive polling

Use DispatchSourceTimer with adaptive polling (0.5s for 30s, then 2s)
instead of Timer for more reliable detection. Add app activation handler
in NotchView to restart fast polling when user returns from System
Settings. Move from custom alert to macOS system prompt for better UX.

* fix: show explanatory alert instead of system dialog on launch

Replace promptForPermission() with showPermissionAlert() to match the
intended behavior described in the PR. Users now see a custom alert
explaining why accessibility permission is needed, with an "Open Settings"
button to guide them to grant permission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant